/*
 * Copyright 2005-2006 webdav-servlet group.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.webduff.locking;

import java.util.Hashtable;

import com.googlecode.webduff.io.URI;


/**
 * some very simple locking management for concurrent data access, NOT the
 * webdav locking. ( could that be used instead? )
 * 
 * @author re
 */
public class ResourceLocks {

    /**
     * after creating this much LockObjects, a cleanup delets unused LockObjects
     */
    private final int fCleanupLimit = 100000;

    protected int fCleanupCounter = 0;
    
    public final static int INFINITE = -1;
    
    public final static int FLAT = 0;

    /**
     * keys: path value: LockObject from that path
     */
    protected Hashtable<String, LockObject> fLocks = null;
    
    protected Hashtable<String, LockObject> fLocksByID = null;

    // REMEMBER TO REMOVE UNUSED LOCKS FROM THE HASHTABLE AS WELL

    protected LockObject fRoot = null;

    public ResourceLocks() {
        fLocks = new Hashtable<String, LockObject>();
        fLocksByID = new Hashtable<String, LockObject>();
        fRoot = new LockObject(this, "/");
    }

    /**
     * trys to lock the resource at "path".
     * 
     * 
     * @param path
     *            what resource to lock
     * @param owner
     *            the owner of the lock
     * @param exclusive
     *            if the lock should be exclusive (or shared)
     * @return true if the resource at path was successfully locked, false if an
     *         existing lock prevented this
     * @param depth depth
     * @throws LockFailedException 
     */
    public synchronized LockObject lock(URI uri, String owner, boolean exclusive, int depth) throws LockFailedException {
        LockObject lo = generateLockObjects(uri);
        if (lo.checkLocks(exclusive, depth)) {
            if (lo.addLockObjectOwner(owner)) {
                lo.fExclusive = exclusive;
                return lo;
            } else {
                throw new LockFailedException();
            }
        } else {
        	throw new LockFailedException();
        }
    }

    public synchronized void unlock(String id, String owner) {
    	if(fLocksByID.containsKey(id)) {
    		//legacy_unlock(fLocksByID.get(id).getPath(), owner);
    		String path = fLocksByID.get(id).getPath();
    		if (fLocks.containsKey(path)) {
                LockObject lo = fLocks.get(path);
                lo.removeLockObjectOwner(owner);
                // System.out.println("number of LockObjects in the hashtable: "
                // + fLocks.size());
            } else {
                // there is no lock at that path. someone tried to unlock it
                // anyway. could point to a problem
                System.out.println("net.sf.webdav.ResourceLocks.unlock(): no lock for path " + path);
            }

            if (fCleanupCounter > fCleanupLimit) {
                fCleanupCounter = 0;
                cleanLockObjects(fRoot);
            }
    	}
    }

    /**
     * generates LockObjects for the resource at path and its parent folders.
     * does not create new LockObjects if they already exist
     * 
     * @param path
     *            path to the (new) LockObject
     * @return the LockObject for path.
     */
    private LockObject generateLockObjects(URI uri) {
    	String path = uri.getPath();
        if (!this.fLocks.containsKey(path)) {
            LockObject returnObject = new LockObject(this, path);
            URI parentUri = uri.getParent();
            if(parentUri != null) {
                LockObject parentLockObject = generateLockObjects(parentUri);
                parentLockObject.addChild(returnObject);
                returnObject.fParent = parentLockObject;
            }
            return returnObject;
        } else {
            return this.fLocks.get(path);
        }

    }

    /**
     * deletes unused LockObjects and resets the counter. works recursively
     * starting at the given LockObject
     * 
     * @param lo lock object
     *
     * @return if cleaned
     */
    private boolean cleanLockObjects(LockObject lo) {

        if (lo.fChildren == null) {
            if (lo.fOwner == null) {
                lo.removeLockObject();
                return true;
            } else {
                return false;
            }
        } else {
            boolean canDelete = true;
            int limit = lo.fChildren.length;
            for (int i = 0; i < limit; i++) {
                if (!cleanLockObjects(lo.fChildren[i])) {
                    canDelete = false;
                } else {

                    // because the deleting shifts the array
                    i--;
                    limit--;
                }
            }
            if (canDelete) {
                if (lo.fOwner == null) {
                    lo.removeLockObject();
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        }
    }

    public LockObject exclusiveLock(URI uri, String owner, int depth) throws LockFailedException {
    	return lock(uri, owner, true, depth);
    }
    
    public LockObject sharedLock(URI uri, String owner, int depth) throws LockFailedException {
    	return lock(uri, owner, false, depth);
    }
    
    public LockObject getLockByID(String id) {
    	if(fLocksByID.containsKey(id)) {
    		return fLocksByID.get(id);
    	}
    	return null;
    }
    
}